package moveresize;

import java.awt.AWTEvent;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;

class MoveResize {
	static final int BORDER_THICKNESS = 4;

	private static Robot robot;
	
	static Rectangle to(Rectangle bounds) {		
		final Object BLOCKER = new Object();
		final Rectangle[] rectangle = new Rectangle[1];
		
		final JFrame desktop = new JFrame();
		desktop.setUndecorated(true);
		desktop.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		desktop.setBounds(0, 0, screenSize.width, screenSize.height);
		
		final Image screenCapture = getRobot().createScreenCapture(desktop.getBounds());
		JLabel screen = new JLabel(
				new ImageIcon(screenCapture));
		desktop.setContentPane(screen);
		
		final TintedPanel window = new TintedPanel();
		
		window.setBounds(bounds);
		Resizer resizer = new Resizer(window);
		resizer.attach();
		BoundsHistory boundsHistory = new BoundsHistory(window);
		boundsHistory.attach();
		
		desktop.getLayeredPane().add(window, JLayeredPane.MODAL_LAYER);
		
		window.addKeyListener(new KeyAdapter() {
			public void keyReleased(KeyEvent e) {
				if (e.getKeyChar() == '?') {
					help(desktop);
				} else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
					close(desktop, window, BLOCKER);
				} else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
					rectangle[0] = window.getBounds();
					close(desktop, window, BLOCKER);
				}
			}
		});
		
        desktop.addKeyListener(new KeyAdapter() {
			public void keyReleased(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
					close(desktop, window, BLOCKER);
				} else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
					rectangle[0] = window.getBounds();
					close(desktop, window, BLOCKER);
				}
			}
		});
        
        desktop.addMouseListener(new MouseAdapter() {
        	public void mouseReleased(MouseEvent e) {
				close(desktop, window, BLOCKER);
        	}
        });
//        boolean fullScreenModeInUse = false;
//        GraphicsDevice graphicsDevice = desktop.getGraphicsConfiguration().getDevice();
//        try {
//            if (graphicsDevice.isFullScreenSupported()) {
//                graphicsDevice.setFullScreenWindow(desktop);
//                fullScreenModeInUse = true;
//            }
            synchronized (BLOCKER) {
            	desktop.setAlwaysOnTop(true);
            	try {
					SwingUtilities.invokeAndWait(new Runnable() {
						public void run() {
							desktop.setVisible(true);
							SwingUtilities.invokeLater(new Runnable() {
			            		public void run() {
			            			Point location = window.getLocation();
			            			Robot robo = getRobot();
			            			robo.mouseMove(location.x + (2*BORDER_THICKNESS), location.y + (2*BORDER_THICKNESS));
			            			robo.mousePress(InputEvent.BUTTON1_MASK);
			            			robo.mouseRelease(InputEvent.BUTTON1_MASK);
			            			window.requestFocusInWindow();
			            		}
			            	});
						}
					});
				} catch (InterruptedException e2) {
				} catch (InvocationTargetException e2) {
				}
            	
            	try {
            		BLOCKER.wait();
            	} catch (InterruptedException e1) {
            	}
            	resizer.dettach();
            	boundsHistory.dettach();
            }
//        } finally {
//            if (fullScreenModeInUse) {
//                graphicsDevice.setFullScreenWindow(null);
//            }
//        }
		return rectangle[0];
	}

	private static void close(final JFrame desktop, JComponent canvas, Object BLOCKER) {
		canvas.setCursor(Cursor.getDefaultCursor());
		desktop.setCursor(Cursor.getDefaultCursor());
		desktop.setVisible(false);
		desktop.dispose();
		System.gc();
        synchronized (BLOCKER) {
        	BLOCKER.notify();
        }
	}
	
	static Cursor getCursor(int c) {
		switch (c) {
		case Cursor.N_RESIZE_CURSOR:
		case Cursor.E_RESIZE_CURSOR:
		case Cursor.W_RESIZE_CURSOR:
		case Cursor.S_RESIZE_CURSOR:
		case Cursor.NE_RESIZE_CURSOR:
		case Cursor.NW_RESIZE_CURSOR:
		case Cursor.SE_RESIZE_CURSOR:
		case Cursor.SW_RESIZE_CURSOR:
		case Cursor.MOVE_CURSOR:
			return Cursor.getPredefinedCursor(c);
		}
		return Cursor.getDefaultCursor();
	}
	
	
	public static Robot getRobot() {
		if (robot == null) {
			try {
				robot = new Robot();
			} catch (AWTException e) {
			}
		}
		return robot;
	}
	
	private static final Object[][] helpRows = new Object[][] {
		 {"Author", "Sandip V. Chitale"},
		 {"Version", "1.3"},
		 {"", ""},
		 {"Direct", "Enter location (x,y) and/or size (width,height) then \u23CE"},
		 {"", ""},
		 {"\u23CE", "accept location and size"},
		 {"\u238B", "cancel"},
		 {"1", "take size of screen minus menubar"},
		 {"2", "take half height and half width of screen"},
		 {"3", "take one third height and one third width of screen"},
		 {"4", "take quarter height and quarter width of screen"},
		 {"5", "take one fifth height and one fifth width of screen"},
		 {"=", "center window"},
		 {"|", "take height of screen"},
		 {"-", "take width of screen"},
		 {"z", "go backwards in the bounds history"},
		 {"y", "go forward in the bounds history"},
		 {"", ""},
		 {"", "Move mode"},
		 {"\u21e1", "move window up by 10 pixels"},
		 {"\u21e3", "move window down by 10 pixels"},
		 {"\u21e0", "move window left by 10 pixels"},
		 {"\u21e2", "move window right by 10 pixels"},
		 {"\u21e7\u21e1", "move window up by 100 pixels"},
		 {"\u21e7\u21e3", "move window down by 100 pixels"},
		 {"\u21e7\u21e0", "move window left by 100 pixels"},
		 {"\u21e7\u21e2", "move window right by 100 pixels"},
		 {"\u2303\u21e1", "move window up by 1 pixel"},
		 {"\u2303\u21e3", "move window down by 1 pixel"},
		 {"\u2303\u21e0", "move window left by 1 pixel"},
		 {"\u2303\u21e2", "move window right by 1 pixel"},
		 {"\u2318\u21e1", "move to the top edge of the screen"},
		 {"\u2318\u21e3", "move to the bottom edge of the screen"},
		 {"\u2318\u21e0", "move to the left edge of the screen"},
		 {"\u2318\u21e2", "move to the right edge of the screen"},
		 {"", ""},
		 {"x", "toggle between Grow (\u25ab \u21e2 \u25a2)and Shrink (\u25a2 \u21e2 \u25ab) mode."},
		 {"", ""},
		 {" \u25ab   \u21e2  \u25a2", "Grow mode (initial mode)"},
		 {"\u2325\u21e1", "grow window by 10 pixels from top edge"},
		 {"\u2325\u21e3", "grow window by 10 pixels from top edge from bottom edge"},
		 {"\u2325\u21e0", "grow window by 10 pixels from top edge from left edge"},
		 {"\u2325\u21e2", "grow window by 10 pixels from top edge from right edge"},
		 {"\u21e7\u2325\u21e1", "grow window by 100 pixels from top edge"},
		 {"\u21e7\u2325\u21e3", "grow window by 100 pixels from bottom edge"},
		 {"\u21e7\u2325\u21e0", "grow window by 100 pixels from left edge"},
		 {"\u21e7\u2325\u21e2", "grow window by 100 pixels from right edge"},
		 {"\u2303\u2325\u21e1", "grow window by 1 pixels from top edge"},
		 {"\u2303\u2325\u21e3", "grow window by 1 pixels from bottom edge"},
		 {"\u2303\u2325\u21e0", "grow window by 1 pixels from left edge"},
		 {"\u2303\u2325\u21e2", "grow window by 1 pixels from right edge "},
		 {"", ""},
		 {" \u25a2  \u21e2  \u25ab", "Shrink mode"},
		 {"\u2325\u21e1", "shrink window by 10 pixels from bottom edge"},
		 {"\u2325\u21e3", "shrink window by 10 pixels from top edge from top edge"},
		 {"\u2325\u21e0", "shrink window by 10 pixels from top edge from right edge"},
		 {"\u2325\u21e2", "shrink window by 10 pixels from top edge from left edge"},
		 {"\u21e7\u2325\u21e1", "shrink window by 100 pixels from bottom edge"},
		 {"\u21e7\u2325\u21e3", "shrink window by 100 pixels from top edge"},
		 {"\u21e7\u2325\u21e0", "shrink window by 100 pixels from right edge"},
		 {"\u21e7\u2325\u21e2", "shrink window by 100 pixels from left edge"},
		 {"\u2303\u2325\u21e1", "shrink window by 1 pixels from bottom edge"},
		 {"\u2303\u2325\u21e3", "shrink window by 1 pixels from top edge"},
		 {"\u2303\u2325\u21e0", "shrink window by 1 pixels from right edge"},
		 {"\u2303\u2325\u21e2", "shrink window by 1 pixels from left edge"},
	};
	
	private static final Object[] columns = new Object[] {
		"Keys", "Description"
	};
	
	private static final Color ODD_ROW_COLOR = new Color(240,240,255);
	
	@SuppressWarnings("serial")
	private static void help(JFrame parent) {		
		final JDialog help = new JDialog(parent, "", true);
		help.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		help.getRootPane().getActionMap().put("hide", 
				new AbstractAction()  {
			public void actionPerformed(ActionEvent e) {
				help.setVisible(false);
			}
		});

		help.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke("released ESCAPE"), "hide");
		
		DefaultTableModel tableModel = new DefaultTableModel(helpRows, columns) {
			public boolean isCellEditable(int row, int column) {
				return false;
			}
		};
		JTable helpTable = new JTable(tableModel);
		helpTable.setDefaultRenderer(Object.class, 
				new DefaultTableCellRenderer() {
			public Component getTableCellRendererComponent(
					JTable table, Object value, boolean isSelected,
					boolean hasFocus, int row, int column) {
				JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
						row, column);
				if (!isSelected) {
					if (row%2 == 0) {
						label.setBackground(ODD_ROW_COLOR);
					} else {
						label.setBackground(null);
					}
				}
				return label;
			}
		});
		helpTable.getColumnModel().getColumn(0).setMaxWidth(150);
		helpTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
		help.setContentPane(new JScrollPane(helpTable));
		help.setBounds(100, 100,500, 550);
		help.setVisible(true);
	}
	
	@SuppressWarnings("serial")
	private static class TintedPanel extends JPanel {
		private static Color TINT = new Color(0, 0, 64, 24);
		private static Color DARK_TINT = new Color(0, 0, 0, 192);
		private static Color DARK = new Color(0, 0, 0, 255);
		private static Color BORDER = new Color(0, 0, 16);
		
		private static Font LARGE_FONT = new Font("Monaco", Font.PLAIN, 16);
		
		private String keyStroke = "";
		
		private AWTEventListener awtEventListener = new AWTEventListener() {
			public void eventDispatched(AWTEvent event) {
				if (event.getSource() == TintedPanel.this) {
					if (event instanceof KeyEvent) {
						KeyEvent keyEvent = (KeyEvent) event;
						String keyText = getKeyText(keyEvent);
						if (keyText != null) {
							keyStroke = keyText;
						}
						repaint();
					}
				}
			}
		};
		
		private static String getKeyText(KeyEvent keyEvent) {
			if (keyEvent.getID() == KeyEvent.KEY_TYPED &&  keyEvent.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
				return null;
			}
			if (keyEvent.getID() == KeyEvent.KEY_RELEASED && 
					(
							keyEvent.getKeyCode() == KeyEvent.VK_SHIFT ||
							keyEvent.getKeyCode() == KeyEvent.VK_CONTROL ||
							keyEvent.getKeyCode() == KeyEvent.VK_ALT ||
							keyEvent.getKeyCode() == KeyEvent.VK_META
							)) {
				return null;
			}
			StringBuilder sb = new StringBuilder();
	        if (keyEvent.getKeyChar() != KeyEvent.CHAR_UNDEFINED && 
	        		((!keyEvent.isAltDown()) 
	        				&& (!keyEvent.isControlDown())
	        				&& (!keyEvent.isMetaDown()))) {
	        	sb.append("" + keyEvent.getKeyChar());
	        } else if (keyEvent.getKeyCode() != KeyEvent.VK_UNDEFINED) {
	        	sb.append(getModifiersText(keyEvent.getModifiers()));
	        	switch (keyEvent.getKeyCode()) {
	        	case KeyEvent.VK_UP:
	        		sb.append("\u21e1");
	        		break;
	        	case KeyEvent.VK_DOWN:
	        		sb.append("\u21e3");
	        		break;
	        	case KeyEvent.VK_LEFT:
	        		sb.append("\u21e0");
	        		break;
	        	case KeyEvent.VK_RIGHT:
	        		sb.append("\u21e2");
	        		break;
	        	case KeyEvent.VK_SHIFT:
	        	case KeyEvent.VK_CONTROL:
	        	case KeyEvent.VK_ALT:
	        	case KeyEvent.VK_ALT_GRAPH:
	        	case KeyEvent.VK_META:
	        		break;
	        	default:
	        		sb.append(getVKText(keyEvent.getKeyCode()).toLowerCase());
	        		break;
	        	}
	        }

	        return sb.toString();
	    }
		
		private static String getModifiersText(int modifiers) {
			StringBuilder sb = new StringBuilder();
			modifiers = mapNewModifiers(mapOldModifiers(modifiers));
	        if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0 ) {
	            sb.append("\u21e7");
	        }
	        if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0 ) {
	        	sb.append("\u2303");
	        }
	        if ((modifiers & InputEvent.META_DOWN_MASK) != 0 ) {
	        	sb.append("\u2318");
	        }
	        if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0 ) {
	        	sb.append("\u2325");
	        }
	        return sb.toString();
	    }
		
		private static int mapOldModifiers(int modifiers) {
			if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
				modifiers |= InputEvent.SHIFT_DOWN_MASK;
			}
			if ((modifiers & InputEvent.ALT_MASK) != 0) {
				modifiers |= InputEvent.ALT_DOWN_MASK;
			}
			if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
				modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
			}
			if ((modifiers & InputEvent.CTRL_MASK) != 0) {
				modifiers |= InputEvent.CTRL_DOWN_MASK;
			}
			if ((modifiers & InputEvent.META_MASK) != 0) {
				modifiers |= InputEvent.META_DOWN_MASK;
			}

			modifiers &= InputEvent.SHIFT_DOWN_MASK
			| InputEvent.ALT_DOWN_MASK
			| InputEvent.ALT_GRAPH_DOWN_MASK
			| InputEvent.CTRL_DOWN_MASK
			| InputEvent.META_DOWN_MASK
			| InputEvent.BUTTON1_DOWN_MASK
			| InputEvent.BUTTON2_DOWN_MASK
			| InputEvent.BUTTON3_DOWN_MASK;

			return modifiers;
		}

		private static int mapNewModifiers(int modifiers) {
			if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) {
				modifiers |= InputEvent.SHIFT_MASK;
			}
			if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0) {
				modifiers |= InputEvent.ALT_MASK;
			}
			if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0) {
				modifiers |= InputEvent.ALT_GRAPH_MASK;
			}
			if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0) {
				modifiers |= InputEvent.CTRL_MASK;
			}
			if ((modifiers & InputEvent.META_DOWN_MASK) != 0) {
				modifiers |= InputEvent.META_MASK;
			}

			return modifiers;
		}
		
		static String getVKText(int keyCode) { 
	        VKCollection vkCollect = getVKCollection();
	        Integer key = new Integer(keyCode);
	        String name = vkCollect.findName(key);
	        if (name != null) {
	            return name.substring(3);
	        }
	        int expected_modifiers = 
	            (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);

	        Field[] fields = KeyEvent.class.getDeclaredFields();
	        for (int i = 0; i < fields.length; i++) {
	            try {
	                if (fields[i].getModifiers() == expected_modifiers
	                    && fields[i].getType() == Integer.TYPE
	                    && fields[i].getName().startsWith("VK_")
	                    && fields[i].getInt(KeyEvent.class) == keyCode) 
	                {
	                    name = fields[i].getName();
	                    vkCollect.put(name, key);
	                    return name.substring(3);
	                }
	            } catch (IllegalAccessException e) {
	                assert(false);
	            }
	        }
	        return "UNKNOWN";
	    }
		
	    /**
	     * Associates VK_XXX (as a String) with code (as Integer). This is
	     * done to avoid the overhead of the reflective call to find the
	     * constant.
	     */
	    private static VKCollection vks;
	    
		private JTextField xField;
		private JTextField yField;
		private JTextField widthField;
		private JTextField heightField;
		private JLabel mode;
		private JLabel key;
		
	    private static VKCollection getVKCollection() {
	        if (vks == null) {
	            vks = new VKCollection();
	        }
	        return vks;
	    }
		
		TintedPanel() {
			super(new GridBagLayout());
			setFocusable(true);
			setOpaque(false);
			setFont(LARGE_FONT);

			GridBagConstraints gridBagConstraints;
			
			JLabel locationLabel = new JLabel("Location: ", JLabel.TRAILING);
			locationLabel.setForeground(Color.white);
			locationLabel.setFont(LARGE_FONT);
			gridBagConstraints = new GridBagConstraints(0,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),2,0);
			add(locationLabel, gridBagConstraints);
			
			xField = new JTextField(5);
			xField.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
			xField.setFont(LARGE_FONT);
			xField.setToolTipText("Enter x location and then \u23CE to move there");
			gridBagConstraints = new GridBagConstraints(1,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(2,2,0,0),2,0);
			
			OvalBorderPanel xFieldOvalBorderPanel = new OvalBorderPanel();
			xFieldOvalBorderPanel.add(xField, BorderLayout.CENTER);
			add(xFieldOvalBorderPanel, gridBagConstraints);
			
			yField = new JTextField(5);
			yField.setFont(LARGE_FONT);
			yField.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
			yField.setToolTipText("Enter y location and then \u23CE to move there");
			gridBagConstraints = new GridBagConstraints(2,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(2,1,0,0),2,0);

			OvalBorderPanel yFieldOvalBorderPanel = new OvalBorderPanel();
			yFieldOvalBorderPanel.add(yField, BorderLayout.CENTER);
			add(yFieldOvalBorderPanel, gridBagConstraints);
			
			JLabel sizeLabel = new JLabel("Size: ", JLabel.TRAILING);
			sizeLabel.setForeground(Color.white);
			sizeLabel.setFont(LARGE_FONT);
			gridBagConstraints = new GridBagConstraints(0,1,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),2,0);
			add(sizeLabel, gridBagConstraints);
			
			widthField = new JTextField(5);
			widthField.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
			widthField.setFont(LARGE_FONT);
			widthField.setToolTipText("Enter width and then \u23CE to resize");
			gridBagConstraints = new GridBagConstraints(1,1,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(1,2,0,0),2,0);
			
			OvalBorderPanel widthFieldOvalBorderPanel = new OvalBorderPanel();
			widthFieldOvalBorderPanel.add(widthField, BorderLayout.CENTER);
			add(widthFieldOvalBorderPanel, gridBagConstraints);
			
			heightField = new JTextField(5);
			heightField.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
			heightField.setFont(LARGE_FONT);
			heightField.setToolTipText("Enter height and then \u23CE to resize");
			gridBagConstraints = new GridBagConstraints(2,1,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(1,1,0,0),2,0);
			
			OvalBorderPanel heightFieldOvalBorderPanel = new OvalBorderPanel();
			heightFieldOvalBorderPanel.add(heightField, BorderLayout.CENTER);
			add(heightFieldOvalBorderPanel, gridBagConstraints);
			
			JLabel modeLabel = new JLabel(" \u2325 Mode: ", JLabel.TRAILING);
			modeLabel.setForeground(Color.white);
			modeLabel.setFont(LARGE_FONT);
			gridBagConstraints = new GridBagConstraints(0,2,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),2,0);
			add(modeLabel, gridBagConstraints);
			
			mode = new JLabel("", JLabel.CENTER);
			mode.setForeground(Color.white);
			mode.setBorder(BorderFactory.createEmptyBorder());
			mode.setFont(LARGE_FONT);
			mode.setToolTipText("Type x to toggle mode");
			gridBagConstraints = new GridBagConstraints(1,2,2,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),2,0);
			add(mode, gridBagConstraints);
			
			JLabel keyLabel = new JLabel("Key: ", JLabel.TRAILING);
			keyLabel.setForeground(Color.white);
			keyLabel.setFont(LARGE_FONT);
			gridBagConstraints = new GridBagConstraints(0,3,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),2,0);
			add(keyLabel, gridBagConstraints);
			
			key = new JLabel(" ", JLabel.CENTER);
			key.setForeground(Color.white);
			key.setFont(LARGE_FONT);
			key.setToolTipText("Displays last typed key stroke");
			gridBagConstraints = new GridBagConstraints(1,3,2,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),2,0);
			add(key, gridBagConstraints);
			
			JLabel helpLabel = new JLabel("Help: ", JLabel.TRAILING);
			helpLabel.setForeground(Color.white);
			helpLabel.setFont(LARGE_FONT);
			gridBagConstraints = new GridBagConstraints(0,4,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),2,0);
			add(helpLabel, gridBagConstraints);
			
			JLabel help = new JLabel("Type ?");
			help.setForeground(Color.white);
			help.setFont(LARGE_FONT);
			gridBagConstraints = new GridBagConstraints(1,4,2,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),2,0);
			add(help, gridBagConstraints);
			
			addComponentListener(new ComponentAdapter() {
				public void componentMoved(ComponentEvent e) {
					showInfo();
				}
				public void componentResized(ComponentEvent e) {
					doLayout();
					showInfo();
				}
				public void componentShown(ComponentEvent e) {
					showInfo();
				}
			});
			
			xField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					modeAndResize();
				}
			});
			
			yField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					modeAndResize();
				}
			});
			
			widthField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					modeAndResize();
				}
			});
			
			heightField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					modeAndResize();
				}
			});
		}
		
		private void modeAndResize() {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					int x = Integer.parseInt(xField.getText());
					int y = Integer.parseInt(yField.getText());
					int width = Integer.parseInt(widthField.getText());
					int height = Integer.parseInt(heightField.getText());
					Rectangle bounds = getBounds();
					bounds.x = x;
					bounds.y = y;
					bounds.width = width;
					bounds.height = height;
					setBounds(bounds);
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							Timer timer = new Timer(500, new ActionListener() {
								public void actionPerformed(ActionEvent e) {
									requestFocusInWindow();
								}
							});
							timer.setRepeats(false);
							timer.start();
						}
					});
				}
			});
		}
		
		public void addNotify() {
			super.addNotify();
			Toolkit.getDefaultToolkit().addAWTEventListener(awtEventListener, AWTEvent.KEY_EVENT_MASK);
		}
		
		public void removeNotify() {
			Toolkit.getDefaultToolkit().removeAWTEventListener(awtEventListener);
			super.removeNotify();
		}
		
		@Override
		public boolean isFocusable() {
			return true;
		}
		
		private void showInfo() {
			final Rectangle bounds = getBounds();
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					if (bounds != null) {
						xField.setText(""+bounds.x);
					}
				}
			});
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					if (bounds != null) {
						yField.setText(""+bounds.y);
					}
				}
			});
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					if (bounds != null) {
						widthField.setText(""+bounds.width);
					}
				}
			});
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					if (bounds != null) {
						heightField.setText(""+bounds.height);
					}
				}
			});
		}

		
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);
			mode.setText(Resizer.isShrink(TintedPanel.this) ? "\u25a2 \u21e2 \u25ab" : " \u25ab \u21e2 \u25a2");
			key.setText(keyStroke);
            if (g instanceof Graphics2D) {
                ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            }
			Rectangle bounds = getBounds();

			g.setColor(TINT);
			g.fillRoundRect(
					0,
					0,
					bounds.width,
					bounds.height,
					BORDER_THICKNESS*2,
					BORDER_THICKNESS*2);
			
			g.setColor(BORDER);
			for (int i = 0; i < 2; i++) {
				g.drawRoundRect(
						i,
						i,
						bounds.width -1 - (2*i),
						bounds.height -1 - (2*i),
						BORDER_THICKNESS*2,
						BORDER_THICKNESS*2);
			}
			
			FontMetrics fontMetrics = g.getFontMetrics();
			Rectangle textExtent = fontMetrics.getStringBounds("                      ", g).getBounds();
			textExtent.height = (textExtent.height * 5) + 8;
			
			int x = ((bounds.width-textExtent.width)/2)-(BORDER_THICKNESS*2);
			int y = ((bounds.height-textExtent.height)/2)-(BORDER_THICKNESS*2);
			if (g instanceof Graphics2D) {
				Graphics2D g2 = (Graphics2D) g;				
				GradientPaint painter = new GradientPaint(x, y, DARK, x, y + textExtent.height+(BORDER_THICKNESS*2), DARK_TINT);
				g2.setPaint(painter);
				g2.fillRoundRect(
						x,
						y+(BORDER_THICKNESS),
						textExtent.width+(BORDER_THICKNESS*3),
						textExtent.height+(BORDER_THICKNESS),
						BORDER_THICKNESS*2,
						BORDER_THICKNESS*2);
			} else {
				g.setColor(DARK);
				g.fillRoundRect(
						x,
						y+(BORDER_THICKNESS),
						textExtent.width+(BORDER_THICKNESS*3),
						textExtent.height+(BORDER_THICKNESS),
						BORDER_THICKNESS*2,
						BORDER_THICKNESS*2);
			}
		}
		
		private static class VKCollection {
			Map<Integer, String> code2name;
		    Map<String, Integer> name2code;

		    public VKCollection() {
		        code2name = new HashMap<Integer, String>();
		        name2code = new HashMap<String, Integer>();
		    }

		    public synchronized void put(String name, Integer code) {
		        assert((name != null) && (code != null));
		        assert(findName(code) == null);
		        assert(findCode(name) == null);
		        code2name.put(code, name);
		        name2code.put(name, code);
		    }

		    public synchronized Integer findCode(String name) {
		        assert(name != null);
		        return (Integer)name2code.get(name);
		    }

		    public synchronized String findName(Integer code) {
		        assert(code != null);
		        return (String)code2name.get(code);
		    }
		}
	}

}
